home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Celestin Apprentice 5
/
Apprentice-Release5.iso
/
Source Code
/
C++
/
Applications
/
NeuroSim 1.0
/
.cp
/
CNeuralNet.cp
< prev
next >
Wrap
Text File
|
1996-02-19
|
13KB
|
396 lines
// ===========================================================================
// CNeuralNet.cp ©1996 Timo Eloranta
// ===========================================================================
// An abstract neural net class - derived from LPeriodical to
// receive a function call (SpendTime()) at regular intervals
#include "CNeuralNet.h"
#include "NS_Utils.h"
#include "CNeuroSimPane.h"
// ---------------------------------------------------------------------------
// • CNeuralNet
//
// Called by: CStdNeuralNet::CStdNeuralNet
// ---------------------------------------------------------------------------
// Constructor. Can't be called with 'new', since CNeuralNet is
// an abstract class.
CNeuralNet::CNeuralNet( Uint16 inSize )
{
mSize = inSize;
mMatrix = NULL;
mPane = NULL;
mDemoMode = false;
// Put this object to the static Idler queue of LPeriodical.
// As a result our SpendTime-function starts getting called
// after every Null Event.
StartIdling();
}
// ---------------------------------------------------------------------------
// • GetNeuron
//
// Called by: CNeuralNet::InitMatrix, GenerateConnections, etc.
// CNeuroSimPane::ClickSelf
// ---------------------------------------------------------------------------
// The matrix of neurons is implemented as an array. This function
// returns a pointer to the neuron in the given column & row.
// Note that there is no bounds checking here because we want this to
// be as fast as possible. Column and row should both be > 0 and <= mSize !!
inline CNeuronPtr
CNeuralNet::GetNeuron( Uint16 inCol, Uint16 inRow ) const
{
return & mMatrix[ inCol - 1 + ((inRow - 1) * mSize) ];
}
// ---------------------------------------------------------------------------
// • RequestDraw
//
// Called by: CNeuralNet::ProcessLightQ
// CStdNeuron::DoClickAction
// ---------------------------------------------------------------------------
// Pass the draw request to the pane.
inline void
CNeuralNet::RequestDraw() const
{
mPane -> InvalidateDrawing();
}
// ---------------------------------------------------------------------------
// • AddToLightQ
//
// Called by: CNeuron::IncState
// CStdNeuron::DoClickAction
// ---------------------------------------------------------------------------
// Add the given neuron pointer (LLink pointer actually) to the end
// of the "light up queue".
inline void
CNeuralNet::AddToLightQ( LLink * inNeuron )
{
mLightQueue.NextPut( inNeuron );
}
// ---------------------------------------------------------------------------
// • ProcessLightQ
//
// Called by: CNeuralNet::SpendTime
// CStdNeuron::DoClickAction
// ---------------------------------------------------------------------------
// Here we process every neuron which has found its way to the
// "light up queue". The main idea is that the processing is done in
// little groups - "waves". A single wave includes all such neurons
// which should light up simultaneously because their lighting up is
// caused by the same neuron or an equally long chain of neurons lighting
// up... The illusion of simultaneus lighting up is achieved by requesting
// the pane to be redrawn only after all the neurons in the same wave have
// been lit up (with the DoLightUpAction function). For a simultaneus
// fading we use a local "fade queue" to which every neuron is added
// after it has been lit up. Waves are processed as long as there are
// new neurons in the "light up queue".
void
CNeuralNet::ProcessLightQ()
{
CNeuronPtr theNeuron;
Uint16 theWaveSize;
LQueue theFadeQueue;
Int32 theDummyTicks;
while ( ! mLightQueue.IsEmpty() ) {
// All neurons which are currently in the queue belong to
// the same "wave". Here we check the size of the wave.
theWaveSize = mLightQueue.GetSize();
for ( int i = 1; i <= theWaveSize; i++ ) {
theNeuron = ( CNeuronPtr ) mLightQueue.NextGet();
theNeuron -> DoLightUpAction();
theFadeQueue.NextPut( theNeuron );
}
// Force redraw and stop for a while so that the user can
// enjoy the full effect of the "animation"...
RequestDraw();
::Delay( 20, &theDummyTicks );
while ( ! theFadeQueue.IsEmpty() ) {
theNeuron = ( CNeuronPtr ) theFadeQueue.NextGet();
theNeuron -> SetPostLightUpState();
}
RequestDraw();
}
}
// ---------------------------------------------------------------------------
// • InitMatrix
//
// Called by: CStdNeuralNet::CStdNeuralNet
// ---------------------------------------------------------------------------
// Derived classes should call this function right after CreateMatrix()
void
CNeuralNet::InitMatrix( const SGenParams & inParams )
{
CNeuronPtr theNeuron;
CNeuron::SetNet( this );
for ( int theRow = 1; theRow <= mSize; theRow++ ) {
for ( int theCol = 1; theCol <= mSize; theCol++ ) {
theNeuron = GetNeuron( theCol, theRow );
theNeuron -> SetNeuronPos( theCol, theRow );
GenerateConnections( *theNeuron, inParams );
}
}
}
// ---------------------------------------------------------------------------
// • ValidLocation
//
// Called by: CNeuralNet::GenerateConnections
// ---------------------------------------------------------------------------
// Check whether a location (column+row) is inside the matrix bounds.
inline Boolean
CNeuralNet::ValidLocation( Int16 inCol, Int16 inRow ) const
{
return ( inCol > 0 && inCol <= mSize &&
inRow > 0 && inRow <= mSize );
}
// ---------------------------------------------------------------------------
// • GenerateConnections
//
// Called by: CNeuralNet::InitMatrix
// ---------------------------------------------------------------------------
// Generate random connections for the given neuron (inNeuron).
// The generation is guided by the parameters (inParams) set by the user.
// We also set the "connection rect" (mConnRect) of the neuron. This
// rectangle is big enough to include all the neurons which inNeuron
// is connected to.
void
CNeuralNet::GenerateConnections(
CNeuron & inNeuron,
const SGenParams & inParams ) const
{
Int16 theQuantity,
theCol, theRow, // Location of inNeuron
theAvgCol, theAvgRow,
theNewCol, theNewRow;
Rect theRect; // Rect which is used for
// calculating the "connection rect"
theQuantity = RangedRdm( inParams.qtyMin, inParams.qtyMax );
inNeuron.GetNeuronPos( theCol, theRow );
SetRect ( &theRect, // Left, top, right, bottom...
theCol, theRow, theCol, theRow );
// Our [1,1] square is in the upper left corner of the matrix, but
// the user doesn't really know this, so we can set the positive
// directions to be RIGHT and UP. Therefore we ADD the average
// "X-length" (columns) and SUBSTRACT the average "Y-length" (rows).
theAvgCol = theCol + inParams.xLengthAvg;
theAvgRow = theRow - inParams.yLengthAvg;
// The location picked by random must...
// • ...be a valid location inside the matrix
// • ...not be the same where inNeuron is situated
// • ...not be a location to which inNeuron already has a connection
//
// Note: If the location is rejected, the loop counter (i) is still
// incresed by one. In other words, theQuantity is actually
// the maximum number of connections which is possible, but
// the neuron gets less connections if any of the randomly
// picked locations are rejected !!
for ( int i = 1; i <= theQuantity; i++ ) {
theNewCol = RangedRdm( theAvgCol - inParams.xLengthDev,
theAvgCol + inParams.xLengthDev );
theNewRow = RangedRdm( theAvgRow - inParams.yLengthDev,
theAvgRow + inParams.yLengthDev );
if ( ValidLocation( theNewCol, theNewRow ) &&
( theNewCol != theCol || theNewRow != theRow ) ) {
CNeuronPtr theNeuron = GetNeuron( theNewCol, theNewRow );
if ( ! inNeuron.IsConnectedTo( theNeuron ) ) {
inNeuron.AddToNeuronList( theNeuron );
// Update the (temporary) connection rect
if ( theNewCol < theRect.left )
theRect.left = theNewCol;
if ( theNewCol > theRect.right )
theRect.right = theNewCol;
if ( theNewRow < theRect.top )
theRect.top = theNewRow;
if ( theNewRow > theRect.bottom )
theRect.bottom = theNewRow;
}
}
}
inNeuron.SetConnectionRect( theRect );
}
// ---------------------------------------------------------------------------
// • GetRandomReceptor
//
// Called by: CNeuralNet::SpendTime
// ---------------------------------------------------------------------------
// Return a pointer to a random receptor
inline CNeuronPtr
CNeuralNet::GetRandomReceptor( ) const
{
Int16 theRow = RangedRdm( 1, mSize );
return GetNeuron( 1, theRow );
}
// ---------------------------------------------------------------------------
// • SpendTime
//
// Called by: LPeriodical::DevoteTimeToIdlers
// ---------------------------------------------------------------------------
// This function overrides the pure virtual function in LPeriodical.
// SpendTime gets called after every single Idle Event (= often...).
// If there aren't any neurons in the "light up queue" and the demo mode
// is on, we pick a random receptor and simulate a click on it.
void
CNeuralNet::SpendTime( const EventRecord & /*inMacEvent*/ )
{
CNeuronPtr theNeuron;
Int32 theDummyTicks;
if (! mLightQueue.IsEmpty())
ProcessLightQ();
else if ( DemoModeOn() ) {
::Delay( 15, &theDummyTicks ); // Wait a moment (1/4 s)...
theNeuron = GetRandomReceptor();
theNeuron -> DoClickAction( false );
}
}
// ---------------------------------------------------------------------------
// • SetNeuronsDirty
//
// Called by: CStdNeuron::DoClickAction
// CStdNeuron::DoLightUpAction
// CStdNeuron::SetPostLightUpState
// ---------------------------------------------------------------------------
// Force every neuron inside the given rectangle to be redrawn.
void
CNeuralNet::SetNeuronsDirty( const Rect & inRect ) const
{
for ( Int16 theRow = inRect.top; theRow <= inRect.bottom; theRow++ )
for ( Int16 theCol = inRect.left; theCol <= inRect.right; theCol++ ) {
CNeuronPtr theNeuron = GetNeuron( theCol, theRow );
theNeuron -> ForceRedraw();
}
}
// ---------------------------------------------------------------------------
// • DrawNeurons
//
// Called by: CNeuroSimPane::DrawNeurons
// ---------------------------------------------------------------------------
// Draw all the neurons which need redrawing. Any neuron's location
// on the screen is calculated from the location of the neuron in the
// first column of the first row (inOneOneRect).
void
CNeuralNet::DrawNeurons(
const Rect &inOneOneRect,
Uint16 inSquareSize ) const
{
Rect theNeuronRect;
CNeuronPtr theNeuron;
for ( Int16 theRow = 1; theRow <= mSize; theRow++ )
for ( Int16 theCol = 1; theCol <= mSize; theCol++ ) {
theNeuron = GetNeuron( theCol, theRow );
if ( theNeuron -> RedrawNeeded() ) {
theNeuronRect = inOneOneRect;
::OffsetRect( &theNeuronRect,
(theCol - 1) * inSquareSize,
(theRow - 1) * inSquareSize );
theNeuron -> Draw( theNeuronRect );
}
}
}
// ---------------------------------------------------------------------------
// • DrawConnections
//
// Called by: CNeuroSimPane::DrawConnections
// ---------------------------------------------------------------------------
// Tell every neuron to redraw its connections - if necessary.
void
CNeuralNet::DrawConnections() const
{
for ( Int16 theRow = 1; theRow <= mSize; theRow++ )
for ( Int16 theCol = 1; theCol <= mSize; theCol++ ) {
CNeuronPtr theNeuron = GetNeuron( theCol, theRow );
if ( theNeuron -> ConnectionsDirty() )
theNeuron -> DrawConnections();
}
}
// ---------------------------------------------------------------------------
// • SetCenterPoints
//
// Called by: CNeuroSimPane::SetNet
// ---------------------------------------------------------------------------
// This function must be called before calling the DrawConnections
// function for the first time. Here we set the center points of the
// neurons. We do this so that we don't have to calculate the center
// points every time we draw the connections...
void
CNeuralNet::SetCenterPoints( Uint16 inOneOneXY, Uint16 inSquareSize) const
{
Point theCenter;
for ( Int16 theRow = 1; theRow <= mSize; theRow++ ) {
for ( Int16 theCol = 1; theCol <= mSize; theCol++ ) {
CNeuronPtr theNeuron = GetNeuron( theCol, theRow );
theCenter.h = inOneOneXY + (theCol - 1) * inSquareSize;
theCenter.v = inOneOneXY + (theRow - 1) * inSquareSize;
theNeuron -> SetCenterPoint( theCenter );
}
}
}